﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using FinPlusCommon;
using log = Logger.Logger;
using u = FinPlusUtility.Utility;

namespace FinPlusComponents
{
    public class QLEngineBond : FinPlusComponent
    {
        private enum Params { Instrument }
        private enum BuildEvents { Update, Refresh, UpdateAndRefresh }
        private readonly IFinPlusComp _instrumentConn, _instrumentBuildConn;
        private readonly QLAnalytics _analytics;
        private readonly Instruments _instruments;
        private readonly BuildEvents _buildEvents;

        //construct
        public QLEngineBond(IFinPlusComp analytics, IFinPlusComp instrumentBuilds, IFinPlusComp instruments, string buildEvents) 
        {
            _analytics = (QLAnalytics)analytics;
            _instrumentBuildConn = instrumentBuilds;
            _instrumentConn = instruments;
            _buildEvents = u.EnumParse<BuildEvents>(buildEvents, BuildEvents.UpdateAndRefresh);
            _instruments = new Instruments();

            foreach (var instrument in _instrumentConn.Adaptor.Where("Name='InstrumentType' And Value='Bond'", Params.Instrument.ToString()))
                _instruments[instrument.Key] = new Instrument(instrument.Key, instrument.Value, _instrumentBuildConn.Adaptor.First("Name='Instrument' And Value='" + instrument.Key + "'"), _analytics, _buildEvents);

            IsValid = true;
        }

        //common control interface
        public override void Dispose()
        {
            try
            {
                if (_analytics != null)
                    _analytics.Dispose();
                if (_instrumentConn != null)
                    _instrumentConn.Dispose();
                if (_instrumentBuildConn != null)
                    _instrumentBuildConn.Dispose();
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Warning, e.Message, e);
            }
        }

        //helper classes
        private class Instruments:Dictionary<string, Instrument>{}

        private class Instrument
        {
            private enum Infos { Settle, End, FunctionName, Instrument, ISIN, SEDOL, CUSIP, BBIdent, Coupon, MktPrice, CouponFrq, Redemption, SettlementDays, EffectiveDate, TerminationDate, DayCount, PayConv, TermConv, Holidays, Unadjusted, Unknown }
            private enum BondType { FixedRateBondBuild, FloatingRateBondBuild, ZeroCouponBondBuild, Unknown };
            private enum Params { LevelUpdate, Refresh, TimeStamp };
            private enum FixedRateBondBuild { BondName, ISIN, SEDOL, CUSIP, BBIdent,  CurveName, Coupon, CouponFrq, Redemption, SettlementDays, EffectiveDate, TerminationDate, DayCount, PayConv, TermConv, BondConv, Holidays }
            private enum FloatingRateBondBuild { BondName, ISIN, SEDOL, CUSIP, BBIdentity, CurveName, Spread, Index, CouponFrq, Redemption, SettlementDays, EffectiveDate, TerminationDate, DayCount, PayConv, TermConv, BondConv, Holidays }
            private enum ZeroCouponBondBuild { BondName, ISIN, SEDOL, CUSIP, BBIdentity, CurveName, Redemption, SettlementDays, EffectiveDate, TerminationDate, BondConv, Holidays }

            private IFunction _instrument, _instrumentBuild;
            private QLAnalytics _analytics;
            private FinPlusLevel _level;
            private BuildEvents _buildEvents;
            private IItem _timeStamp;
            private int _effective, _settle, _end;
            private string _name;
            private bool _hasInitialised;

            public Instrument(string name, IFunction instrument, IFunction instrumentBuild, QLAnalytics analytics, BuildEvents buildEvents)
            {
                _name = name;
                _analytics = analytics;
                _instrument = instrument;
                _instrument.Add(_analytics.Infos);
                _instrument.Add(_analytics.Controls);

                _instrument.Get(Params.LevelUpdate.ToString(), new Function(), Instrument_Update);

                if (buildEvents == BuildEvents.Refresh || buildEvents == BuildEvents.UpdateAndRefresh)
                    _instrument.Get(Params.Refresh.ToString(), string.Empty, Instrument_Refresh);

                _timeStamp = _instrument.Get(Params.TimeStamp.ToString());
                _level = new FinPlusLevel(_analytics.BuySell);
                _instrumentBuild = instrumentBuild;
                _buildEvents = buildEvents;
                Info();
            }

            //private
            private void UpdateFixedRateBond()
            {
                try
                {
                    string res;

                    lock (_analytics.QL)
                    {
                        //bond object
                        res = _analytics.QL.FixedRateBondBuild(_analytics.MarketName, _name,
                                (double)_instrumentBuild.Value(FixedRateBondBuild.Coupon.ToString()), _level.Value,
                                _instrumentBuild[FixedRateBondBuild.CouponFrq.ToString()].ToString(),
                                (double)_instrumentBuild.Value(FixedRateBondBuild.Redemption.ToString()),
                                (int)_instrumentBuild.Value(FixedRateBondBuild.SettlementDays.ToString()),
                                _effective, _end, _instrumentBuild[FixedRateBondBuild.DayCount.ToString()].ToString(),
                                _instrumentBuild[FixedRateBondBuild.PayConv.ToString()].ToString(),
                                _instrumentBuild[FixedRateBondBuild.TermConv.ToString()].ToString(),
                                _instrumentBuild[FixedRateBondBuild.BondConv.ToString()].ToString(),
                                _instrumentBuild[FixedRateBondBuild.Holidays.ToString()].ToString());

                        //temp trade for calcs
                        if (!_hasInitialised)
                            res = _analytics.QL.FixedRateBond(_analytics.MarketName, "Temp", _name, _name, _instrumentBuild[FixedRateBondBuild.CurveName.ToString()].ToString(), 100, _settle, 100);
                    }

                    _hasInitialised = true;
                }
                catch (Exception e)
                {
                    throw new Exception(string.Format("FixedRateBondBuild build failed {0}", _name), e);        
                }
            }

            private void UpdateFloatingRateBond()
            {
                try
                {
                    string res;

                    lock (_analytics.QL)
                    {
                        //bond object
                        res = _analytics.QL.FloatingRateBondBuild(_analytics.MarketName, _name,
                                 (double)_instrumentBuild.Value(FloatingRateBondBuild.Spread.ToString()),
                                 _instrumentBuild[FloatingRateBondBuild.CurveName.ToString()].ToString(),
                                 _instrumentBuild[FloatingRateBondBuild.Index.ToString()].ToString(),
                                 _instrumentBuild[FloatingRateBondBuild.CouponFrq.ToString()].ToString(),
                                 (double)_instrumentBuild.Value(FloatingRateBondBuild.Redemption.ToString()),
                                 (int)_instrumentBuild.Value(FloatingRateBondBuild.SettlementDays.ToString()),
                                 _effective, _end, _instrumentBuild[FloatingRateBondBuild.DayCount.ToString()].ToString(),
                                 _instrumentBuild[FloatingRateBondBuild.PayConv.ToString()].ToString(),
                                 _instrumentBuild[FloatingRateBondBuild.TermConv.ToString()].ToString(),
                                 _instrumentBuild[FloatingRateBondBuild.BondConv.ToString()].ToString(),
                                 _instrumentBuild[FloatingRateBondBuild.Holidays.ToString()].ToString());

                        //temp trade for calcs
                        if (!_hasInitialised)
                            res = _analytics.QL.FloatingRateBond(_analytics.MarketName, "Temp", _name, _name, _instrumentBuild[FloatingRateBondBuild.CurveName.ToString()].ToString(), 100, _settle, 100);
                    }
                    _hasInitialised = true;
                }
                catch (Exception e)
                {
                    throw new Exception(string.Format("FloatingRateBondBuild build failed {0}", _name), e);        
                }
            }

            private void UpdateZeroCouponRateBond()
            {
                try
                {
                    string res;

                    lock (_analytics.QL)
                    {
                        //bond object
                        res = _analytics.QL.ZeroCouponBondBuild(_analytics.MarketName, _name,
                                (double)_instrumentBuild.Value(ZeroCouponBondBuild.Redemption.ToString()),
                                (int)_instrumentBuild.Value(ZeroCouponBondBuild.SettlementDays.ToString()),
                                _effective, _end, _instrumentBuild[ZeroCouponBondBuild.BondConv.ToString()].ToString(),
                                _instrumentBuild[ZeroCouponBondBuild.Holidays.ToString()].ToString());

                        //temp trade for calcs
                        if (!_hasInitialised)
                            res = _analytics.QL.ZeroCouponBond(_analytics.MarketName, "Temp", _name, _name, _instrumentBuild[ZeroCouponBondBuild.CurveName.ToString()].ToString(), 100, _settle, 100);
                    }
                    _hasInitialised = true;
                }
                catch (Exception e)
                {
                    throw new Exception(string.Format("ZeroCouponBondBuild build failed {0}", _name), e);        
                }
            }

            private void Info()
            {
                if (_instrumentBuild == null) return;

                _effective = (int)((DateTime)_instrumentBuild.Value(Infos.EffectiveDate.ToString())).ToOADate();
                
                lock (_analytics.QL)
                    _settle = _analytics.QL.NextBizDate(_analytics.AsOf, _instrumentBuild[Infos.SettlementDays.ToString()].ToString() + "D", Infos.Unadjusted.ToString(), false, _instrumentBuild[Infos.Holidays.ToString()].ToString());
                
                if (_settle < _effective) 
                    _settle = _effective;
                
                _end = (int)((DateTime)_instrumentBuild.Value(Infos.TerminationDate.ToString())).ToOADate();

                foreach (var kvp in _analytics.Infos)
                {
                    var val = u.EnumParse<Infos>(kvp.Key, Infos.Unknown);
                    switch (val)
                    {
                        case Infos.Settle: _instrument.Get(Infos.Settle.ToString()).Set(_settle); break;
                        case Infos.End: _instrument.Get(Infos.End.ToString()).Set(_end); break;
                        case Infos.Unknown: _instrument.Get(Infos.Unknown.ToString()).Set(Infos.Unknown.ToString()); break;
                        default: _instrument.Get(val.ToString()).Set(_instrumentBuild[val.ToString()]); break;//build info
                    }
                }
            }

            private void Value()
            {
                switch (u.EnumParse<BondType>(_instrumentBuild[Infos.FunctionName.ToString()].ToString(), BondType.Unknown))
                {
                    case BondType.FixedRateBondBuild: UpdateFixedRateBond(); break;
                    case BondType.FloatingRateBondBuild: UpdateFloatingRateBond(); break;
                    case BondType.ZeroCouponBondBuild: UpdateZeroCouponRateBond(); break;
                    case BondType.Unknown: throw new Exception(string.Format("Unknown bond build {0}", _name));
                }

                if (!_analytics.HasControlString) return;

                string r;
                lock (_analytics.QL)
                    r = _analytics.QL.MarketValueTrade(_analytics.MarketName, "Temp", _name, _analytics.ControlString);

                var res = (string[,])u.StringToArray2d(r, ';', ',');
                for (int x = 1; x <= res.GetUpperBound(1); x++)
                    _instrument.Get(res[0, x]).Set(res[1, x]);
            }

            //events
            private void Instrument_Update(object s, PropertyChangedEventArgs a)
            {
                try
                {
                    if(!_level.LevelUpdate((IFunction)((IItem)s).Object)) return;

                    if (_buildEvents != BuildEvents.Update && _buildEvents != BuildEvents.UpdateAndRefresh) return;

                    if (Double.IsNaN(_level.Value)) return;

                    Value();

                    _timeStamp.Set(_level.TimeStamp);
                    
                }
                catch (Exception e)
                {
                    log.Log(Logger.Level.Error, e.Message, e);
                }
            }

            private void Instrument_Refresh(object s, PropertyChangedEventArgs a)
            {
                if (((IItem)s).Value.Equals(_name)) 
                    Value();
            }
        }
    }
}
